﻿using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HeroldIT.Dnn.Modules.WebControls
{
    /// <summary>
    /// A hierarchical DropDownList control.
    /// </summary>
    public class HierarchicalDropDownList : Panel, INamingContainer
    {
        #region control fields

        private UpdatePanel upnMasterDropDown;

        private DropDownList ddlMasterDropDown;

        private UpdatePanel upnSlaveDropDown;

        private DropDownList ddlSlaveDropDown;

        #endregion

        #region fields

        private bool slaveDataRequested;

        #endregion

        #region properties

        #region Master DropDownList properties

        /// <summary>
        /// Gets or sets the object from which the master DropDownList
        /// retrieves its list of items.
        /// </summary>
        public object DataSource
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.DataSource;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.DataSource = value;
            }
        }

        /// <summary>
        /// Gets or set the name of the list of data that the master
        /// DropDownList binds to, in cases where the data source contains more
        /// than one distinct list of data items.
        /// </summary>
        public string DataMember
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.DataMember;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.DataMember = value;
            }
        }

        /// <summary>
        /// Gets or sets the field of the master DropDownList's data source that 
        /// provides the text content of the list items.
        /// </summary>
        public string DataTextField
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.DataTextField;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.DataTextField = value;
            }
        }

        /// <summary>
        /// Gets or sets the formatting string used to control how data
        /// bound to the master DropDownList is displayed.
        /// </summary>
        public string DataTextFormatString
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.DataTextFormatString;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.DataTextFormatString = value;
            }
        }

        /// <summary>
        /// Gets or sets the field of the master DropDownList's data source that
        /// provides the value of each list item.
        /// </summary>
        public string DataValueField
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.DataValueField;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.DataValueField = value;
            }
        }

        /// <summary>
        /// Gets the collection of items in the master DropDownList.
        /// </summary>
        public ListItemCollection Items
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.Items;
            }
        }

        /// <summary>
        /// Gets the value of the selected item in the master DropDownList,
        /// or selects the item in the master DropDownList that contains the
        /// specified value.
        /// </summary>
        public string SelectedValue
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlMasterDropDown.SelectedValue;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlMasterDropDown.SelectedValue = value;
                this.PopulateSlaveDropDown();
            }
        }

        #endregion

        #region Slave DropDownList properties

        /// <summary>
        /// Gets or set the name of the list of data that the slave
        /// DropDownList binds to, in cases where the data source contains more
        /// than one distinct list of data items.
        /// </summary>
        public string SlaveDataMember
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlSlaveDropDown.DataMember;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlSlaveDropDown.DataMember = value;
            }
        }

        /// <summary>
        /// Gets or sets the field of the slave DropDownList's data source that 
        /// provides the text content of the list items.
        /// </summary>
        public string SlaveDataTextField
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlSlaveDropDown.DataTextField;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlSlaveDropDown.DataTextField = value;
            }
        }

        /// <summary>
        /// Gets or sets the formatting string used to control how data
        /// bound to the slave DropDownList is displayed.
        /// </summary>
        public string SlaveDataTextFormatString
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlSlaveDropDown.DataTextFormatString;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlSlaveDropDown.DataTextFormatString = value;
            }
        }

        /// <summary>
        /// Gets or sets the field of the slave DropDownList's data source that
        /// provides the value of each list item.
        /// </summary>
        public string SlaveDataValueField
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlSlaveDropDown.DataValueField;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlSlaveDropDown.DataValueField = value;
            }
        }

        /// <summary>
        /// Gets the value of the selected item in the slave DropDownList,
        /// or selects the item in the slave DropDownList that contains the
        /// specified value.
        /// </summary>
        public string SelectedSlaveValue
        {
            get
            {
                this.EnsureChildControls();
                return this.ddlSlaveDropDown.SelectedValue;
            }
            set
            {
                this.EnsureChildControls();
                this.ddlSlaveDropDown.SelectedValue = value;
            }
        }

        #endregion

        private string validationGroup;

        /// <summary>
        /// Gets or sets the group of controls for which the control causes validation
        /// when it posts back to the server.
        /// </summary>
        public string ValidationGroup
        {
            get
            {
                return this.validationGroup;
            }
            set
            {
                this.validationGroup = value;
                if (this.ChildControlsCreated)
                {
                    this.ddlMasterDropDown.ValidationGroup = value;
                    this.ddlSlaveDropDown.ValidationGroup = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the width of the Web server control.
        /// </summary>
        /// <returns>A <see cref="T:System.Web.UI.WebControls.Unit" /> that represents the width of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty" />.</returns>
        /// <exception cref="T:System.ArgumentException">The width of the Web server control was set to a negative value.</exception>
        public override Unit Width
        {
            get
            {
                return base.Width;
            }
            set
            {
                base.Width = value;
                if (this.ChildControlsCreated)
                {
                    this.ddlMasterDropDown.Width = value;
                    this.ddlSlaveDropDown.Width = value;
                }
            }
        }

        #endregion

        #region events

        /// <summary>
        /// Fires when data for the slave DropDownList is required.
        /// </summary>
        public event EventHandler<HierarchicalDataEventArgs> SlaveDataRequested;

        /// <summary>
        /// Fires when the selected value of at least one of the DropDownLists has changed.
        /// </summary>
        public event EventHandler SelectedValueChanged;

        #endregion

        #region page lifecycle

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Init" /> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs" /> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            this.EnsureChildControls();

            base.OnInit(e);
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
        /// </summary>
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            this.ddlMasterDropDown = new DropDownList
                                         {
                                             ID = "ddlMasterDropDown",
                                             AppendDataBoundItems = false,
                                             AutoPostBack = true,
                                             ValidationGroup = this.validationGroup,
                                             Width = this.Width,
                                             CssClass = "hdd-MasterDropDown"
                                         };
            this.ddlMasterDropDown.SelectedIndexChanged += ddlMasterDropDown_SelectedIndexChanged;

            this.upnMasterDropDown = new UpdatePanel
                                         {
                                             ID = "upnMasterDropDown",
                                             ChildrenAsTriggers = true,
                                             RenderMode = UpdatePanelRenderMode.Block,
                                             UpdateMode = UpdatePanelUpdateMode.Always,
                                             ContentTemplate =
                                                 new CompiledTemplateBuilder(
                                                 control => control.Controls.Add(this.ddlMasterDropDown))
                                         };
            this.Controls.Add(this.upnMasterDropDown);

            this.ddlSlaveDropDown = new DropDownList
                                        {
                                            ID = "ddlSlaveDropDown",
                                            AppendDataBoundItems = false,
                                            AutoPostBack = true,
                                            ValidationGroup = this.validationGroup,
                                            Width = this.Width,
                                            CssClass = "hdd-SlaveDropDown"
                                        };
            this.ddlSlaveDropDown.SelectedIndexChanged += this.ddlSlaveDropDown_SelectedIndexChanged;

            this.upnSlaveDropDown = new UpdatePanel
                                        {
                                            ID = "upnSlaveDropDown",
                                            ChildrenAsTriggers = true,
                                            RenderMode = UpdatePanelRenderMode.Block,
                                            UpdateMode = UpdatePanelUpdateMode.Always,
                                            ContentTemplate =
                                                new CompiledTemplateBuilder(
                                                control => control.Controls.Add(this.ddlSlaveDropDown))
                                        };
            this.Controls.Add(this.upnSlaveDropDown);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.PreRender" /> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs" /> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            this.EnsureChildControls();

            if (!this.slaveDataRequested)
            {
                this.PopulateSlaveDropDown();
            }
            this.ddlSlaveDropDown.Enabled = (null != this.ddlSlaveDropDown.DataSource);
        }

        #endregion

        #region methods

        /// <summary>
        /// Raises the <see cref="SlaveDataRequested"/> event.
        /// </summary>
        /// <param name="e">An <see cref="HierarchicalDataEventArgs"/> object that contains the event data.</param>
        protected void OnSlaveDataRequested(HierarchicalDataEventArgs e)
        {
            this.slaveDataRequested = true;
            if (null != this.SlaveDataRequested)
            {
                this.SlaveDataRequested(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="SelectedValueChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected void OnSelectedValueChanged(EventArgs e)
        {
            if (null != this.SelectedValueChanged)
            {
                this.SelectedValueChanged(this, e);
            }
        }

        private void PopulateSlaveDropDown()
        {
            HierarchicalDataEventArgs e = new HierarchicalDataEventArgs(this.ddlMasterDropDown.SelectedValue);
            this.OnSlaveDataRequested(e);
            string previousSlaveValue = this.SelectedSlaveValue;
            this.ddlSlaveDropDown.Items.Clear();
            this.ddlSlaveDropDown.DataSource = e.SlaveDataSource;
            this.ddlSlaveDropDown.DataBind();
            ListItem listItem = this.ddlSlaveDropDown.Items.FindByValue(previousSlaveValue);
            if (null != listItem)
            {
                listItem.Selected = true;
            }
        }

        #endregion

        #region event handlers

        private void ddlMasterDropDown_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.PopulateSlaveDropDown();
            this.OnSelectedValueChanged(EventArgs.Empty);
        }

        void ddlSlaveDropDown_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.OnSelectedValueChanged(EventArgs.Empty);
        }

        #endregion
    }
}
